/*
 * BackupProjectSchedulerTask.java
 *
 * Created on Oct 12, 2007, 8:36:09 PM
 *
 * Copyright (c) October 14, 2007 by Dean Del Ponte
 * http://www.sourceseed.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published
 * by  the Free Software Foundation; either version 3 of the License or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public License
 * along with this program; see the file COPYING.TXT.  If not, write to
 * the Free Software Foundation Inc., 59 Temple Place - Suite 330,
 * Boston, MA  02111-1307 USA
 */
package org.sourceseed.tools.backup.tasks;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.vfs.AllFileSelector;
import org.apache.commons.vfs.FileObject;
import org.apache.commons.vfs.FileSystemException;
import org.apache.commons.vfs.FileType;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import org.tiling.scheduling.*;
import org.sourceseed.tools.backup.backThatThingUp.Project;
import org.joda.time.DateTime;
import org.sourceseed.tools.VfsUtils;
import org.sourceseed.tools.backup.backThatThingUp.Destination;

/**
 * Task which is run whenever a backup is triggered.  This task is responsible <br\>
 * for everything including:<br\>
 * <ol>
 * <li>Zipping up targets</li>
 * <li>Copying backup file to appropriate destinations</li>
 * <li>Enforcing data retention limits</li>
 * <li>Sending email</li>
 * </ol>
 *
 * @author Dean Del Ponte
 */
public class BackupProjectSchedulerTask extends SchedulerTask {

    static Logger logger = Logger.getLogger(BackupProjectSchedulerTask.class);
    private Project project;
    private SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
    private String backupFileName;
    private VfsUtils vfsUtils = new VfsUtils();

    /**
     * 
     * @param project
     */
    public BackupProjectSchedulerTask(Project project) {
        this.project = project;
        PropertyConfigurator.configure("config" + File.separator + "log4j.properties");
    }

    /**
     * Runs the backup in its own thread
     * Performs the following tasks:<br \>
     * <ol>
     * <li>Zips up targets (directories and files) to destinations</li>
     * <li>Sends email notifications to appropriate recipients</li>
     * </ol>
     */
    public void run() {
        backupFileName = project.getName() + "_" + sdf.format(new Date()) + ".zip";
        try {
            // 1.  Zip up the targets (directories and files)
            // 2.  Copy zipped targets to the destinations
            // 3.  Send out email notifications            
            zip(project.getTargets());
        } catch (IOException ex) {
            logger.error("Error backup up project " + project.getName(), ex);
        } catch (IllegalArgumentException ex) {
            logger.error("Error backup up project " + project.getName(), ex);
        }
    }

    /**
     * 
     * @return Project
     */
    public Project getProject() {
        return project;
    }

    /**
     * 
     * @param project
     */
    public void setProject(Project project) {
        this.project = project;
    }

    /**
     * <ol>
     * <li>Get target files</li>
     * <li>Enforce data retention limits</li>
     * <li>Zip target files to destination locations</li>
     * </ol>
     * @param targetFilePaths
     * @throws java.io.IOException
     * @throws java.lang.IllegalArgumentException
     */
    private void zip(Collection<String> targetFilePaths)
            throws IOException, IllegalArgumentException {
        Collection<File> files = new ArrayList<File>();

        for (String filePath : targetFilePaths) {
            files.add(new File(filePath));
        }

        // Specify files to be zipped
        Collection<String> filesToZip = getFilesToZip(files);
        enforceDataRetentionLimits();
        vfsUtils.createZips(filesToZip, project, backupFileName);
    }

    private Collection<String> getFilesToZip(Collection<File> files) {
        List<String> list = new ArrayList<String>();

        for (File file : files) {
            if (file.isFile()) {
                try {
                    list.add(file.getCanonicalPath());
                } catch (IOException ex) {
                    logger.error("Error reading targets for " + project.getName(), ex);
                }
            } else {
                list.addAll(getFilesToZip(FileUtils.listFiles(file, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE)));
            }
        }
        return list;
    }

    /**
     * 
     * @return Collection of file objects to be deleted
     */
    private void enforceDataRetentionLimits() {
        try {
            // 1.  Check files in destination and determine which ones to remove based on time
            //      and totals.  If either is exceeded, those files are deleted

            for (Destination destination : project.getDestinations()) {
                FileObject zipFileObject = destination.getFileObject();
                FileObject[] fileObjects = zipFileObject.findFiles(new AllFileSelector());
                if (fileObjects != null) {
                    purgeFiles(fileObjects);
                }
            }
        } catch (FileSystemException ex) {
            logger.error("Error endorcing data retention limits for project " + project.getName(), ex);
        }
    }

    private void purgeFiles(FileObject[] fileObjects) {
        Map<DateTime, FileObject> sortedFiles = new TreeMap<DateTime, FileObject>();

        for (FileObject fileObject : fileObjects) {
            try {
                if (fileObject.getType() == FileType.FILE) {
                    DateTime key = new DateTime(fileObject.getContent().getLastModifiedTime());
                    while (sortedFiles.containsKey(key)) {
                        key = key.plusMillis(1);
                    }
                    sortedFiles.put(key, fileObject);
                }
            } catch (FileSystemException ex) {
                logger.error("Error purging files for project " + project.getName(), ex);
            }
        }

        purgeOldFiles(sortedFiles);
        purgeExcessFiles(sortedFiles);
    }

    private void purgeOldFiles(Map<DateTime, FileObject> sortedFiles) {
        int totalRetentionDays = project.getDataRetentionLimit().getTotalDays();
        if (totalRetentionDays > 0) {
            DateTime currentDate = new DateTime();
            DateTime cutoffDate = currentDate.minusDays(totalRetentionDays);

            for (DateTime date : sortedFiles.keySet()) {
                if (date.isBefore(cutoffDate)) {
                    FileObject fo = sortedFiles.get(date);
                    vfsUtils.deleteFileObject(fo);
                }

            }
        }
    }

    private void purgeExcessFiles(Map<DateTime, FileObject> sortedFiles) {
        int totalFilesToRemove = sortedFiles.size() -
                project.getDataRetentionLimit().getTotalFiles() + 1;

        if (totalFilesToRemove > 0) {
            Iterator itr = sortedFiles.keySet().iterator();
            for (int i = 0; i < totalFilesToRemove; i++) {
                if (itr.hasNext()) {
                    FileObject fileObject = sortedFiles.get(itr.next());
                    vfsUtils.deleteFileObject(fileObject);
                }
            }
        }
    }
}
